"""
Tests for LLM provider auto-discovery system.
"""

import pytest
from unittest.mock import Mock, patch

from src.local_deep_research.llm.providers.auto_discovery import (
    ProviderInfo,
    ProviderDiscovery,
    discover_providers,
    get_discovered_provider_options,
    get_provider_class,
)


class TestProviderInfo:
    """Tests for ProviderInfo class."""

    def test_provider_info_from_class(self, mock_provider_class):
        """Creates ProviderInfo from provider class."""
        info = ProviderInfo(mock_provider_class)
        assert info.provider_key == "MOCK"
        assert info.provider_name == "Mock Provider"
        assert info.company_name == "Mock Company"
        assert info.region == "Test Region"
        assert info.country == "Test Country"
        assert info.gdpr_compliant is True
        assert info.is_cloud is True

    def test_provider_info_default_provider_key(self):
        """Generates default provider key from class name."""

        class TestProvider:
            provider_name = "Test Provider"
            # No explicit provider_key

        info = ProviderInfo(TestProvider)
        assert info.provider_key == "TEST"

    def test_provider_info_to_dict(self, mock_provider_class):
        """Converts to dictionary for API responses."""
        info = ProviderInfo(mock_provider_class)
        result = info.to_dict()

        assert "value" in result
        assert "label" in result
        assert "is_cloud" in result
        assert "region" in result
        assert "country" in result
        assert "gdpr_compliant" in result
        assert "data_location" in result

        assert result["value"] == "MOCK"
        assert result["is_cloud"] is True
        assert result["gdpr_compliant"] is True

    def test_display_name_generation_cloud(self, mock_provider_class):
        """Generates display name for cloud provider."""
        info = ProviderInfo(mock_provider_class)
        # Should contain provider name and cloud indicator
        assert "Mock Provider" in info.display_name
        assert "☁️" in info.display_name or "Cloud" in info.display_name

    def test_display_name_generation_local(self, mock_local_provider_class):
        """Generates display name for local provider."""
        info = ProviderInfo(mock_local_provider_class)
        # Should contain provider name and local indicator
        assert "Mock Local" in info.display_name
        assert "💻" in info.display_name or "Local" in info.display_name

    def test_requires_auth_for_models_with_method(self, mock_provider_class):
        """Uses requires_auth_for_models method if available."""
        info = ProviderInfo(mock_provider_class)
        assert info.requires_auth_for_models is True

    def test_requires_auth_for_models_without_method(self):
        """Defaults to True if method not available."""

        class SimpleProvider:
            provider_name = "Simple"
            # No requires_auth_for_models method

        info = ProviderInfo(SimpleProvider)
        assert info.requires_auth_for_models is True


class TestProviderDiscovery:
    """Tests for ProviderDiscovery class."""

    @pytest.fixture
    def discovery(self):
        """Create fresh ProviderDiscovery instance."""
        # Reset singleton for testing
        ProviderDiscovery._instance = None
        ProviderDiscovery._providers = {}
        return ProviderDiscovery()

    def test_singleton_pattern(self, discovery):
        """ProviderDiscovery is a singleton."""
        discovery2 = ProviderDiscovery()
        assert discovery is discovery2

    def test_discover_providers_returns_dict(self, discovery):
        """discover_providers returns a dictionary."""
        # Mock the implementations directory
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            result = discovery.discover_providers()
            assert isinstance(result, dict)

    def test_discover_providers_caches_result(self, discovery):
        """discover_providers caches results."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            discovery.discover_providers()

            # Second call shouldn't re-scan
            discovery.discover_providers()
            assert mock_glob.call_count == 1

    def test_discover_providers_force_refresh(self, discovery):
        """discover_providers with force_refresh re-scans."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            discovery.discover_providers()
            discovery.discover_providers(force_refresh=True)
            assert mock_glob.call_count == 2

    def test_get_provider_info_not_found(self, discovery):
        """get_provider_info returns None for unknown provider."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            discovery.discover_providers()
            result = discovery.get_provider_info("NONEXISTENT")
            assert result is None

    def test_get_provider_info_case_insensitive(self, discovery):
        """get_provider_info is case-insensitive."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            discovery.discover_providers()

            # Add a mock provider
            mock_info = Mock()
            discovery._providers["TEST"] = mock_info

            # Should find it regardless of case
            assert discovery.get_provider_info("test") is mock_info
            assert discovery.get_provider_info("TEST") is mock_info
            assert discovery.get_provider_info("Test") is mock_info

    def test_get_provider_options_returns_list(self, discovery):
        """get_provider_options returns a sorted list."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            discovery.discover_providers()
            result = discovery.get_provider_options()
            assert isinstance(result, list)

    def test_get_provider_options_sorted(self, discovery):
        """get_provider_options returns sorted by label."""
        # Add mock providers with specific labels
        mock_info_a = Mock()
        mock_info_a.to_dict.return_value = {
            "value": "A",
            "label": "Alpha Provider",
        }

        mock_info_z = Mock()
        mock_info_z.to_dict.return_value = {
            "value": "Z",
            "label": "Zeta Provider",
        }

        discovery._discovered = True
        discovery._providers = {"A": mock_info_a, "Z": mock_info_z}

        result = discovery.get_provider_options()
        assert result[0]["label"] == "Alpha Provider"
        assert result[1]["label"] == "Zeta Provider"

    def test_get_provider_class_found(self, discovery, mock_provider_class):
        """get_provider_class returns class for known provider."""
        mock_info = Mock()
        mock_info.provider_class = mock_provider_class

        discovery._discovered = True
        discovery._providers = {"MOCK": mock_info}

        result = discovery.get_provider_class("MOCK")
        assert result is mock_provider_class

    def test_get_provider_class_not_found(self, discovery):
        """get_provider_class returns None for unknown provider."""
        discovery._discovered = True
        discovery._providers = {}

        result = discovery.get_provider_class("NONEXISTENT")
        assert result is None


class TestModuleFunctions:
    """Tests for module-level convenience functions."""

    @pytest.fixture(autouse=True)
    def reset_singleton(self):
        """Reset singleton before each test."""
        ProviderDiscovery._instance = None
        ProviderDiscovery._providers = {}
        yield

    def test_discover_providers_function(self):
        """discover_providers() function works."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            result = discover_providers()
            assert isinstance(result, dict)

    @pytest.mark.skip(
        reason="Singleton caching prevents force_refresh from working as expected in tests"
    )
    def test_discover_providers_force_refresh(self):
        """discover_providers() supports force_refresh."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            discover_providers()
            discover_providers(force_refresh=True)
            assert mock_glob.call_count == 2

    def test_get_discovered_provider_options_function(self):
        """get_discovered_provider_options() function works."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            result = get_discovered_provider_options()
            assert isinstance(result, list)

    def test_get_provider_class_function(self):
        """get_provider_class() function works."""
        with patch("pathlib.Path.glob") as mock_glob:
            mock_glob.return_value = []
            result = get_provider_class("NONEXISTENT")
            assert result is None


class TestProviderDiscoveryIntegration:
    """Integration tests for provider discovery."""

    @pytest.fixture(autouse=True)
    def reset_singleton(self):
        """Reset singleton before each test."""
        ProviderDiscovery._instance = None
        ProviderDiscovery._providers = {}
        yield

    def test_discovers_real_providers(self):
        """Discovers actual provider implementations."""
        # This test uses the real implementations directory
        result = discover_providers(force_refresh=True)

        # Should find at least some providers
        assert len(result) > 0

        # Check that providers have expected structure
        for key, info in result.items():
            assert isinstance(key, str)
            assert hasattr(info, "provider_key")
            assert hasattr(info, "provider_name")
            assert hasattr(info, "to_dict")

    def test_discovered_providers_have_required_attributes(self):
        """Discovered providers have all required attributes."""
        result = discover_providers(force_refresh=True)

        for key, info in result.items():
            # All providers should have these
            assert info.provider_key is not None
            assert info.provider_name is not None
            assert info.display_name is not None
            assert info.is_cloud in [True, False]

            # to_dict should return valid structure
            dict_result = info.to_dict()
            assert "value" in dict_result
            assert "label" in dict_result

    def test_provider_options_for_dropdown(self):
        """Provider options are suitable for UI dropdowns."""
        discover_providers(force_refresh=True)
        options = get_discovered_provider_options()

        assert isinstance(options, list)

        for option in options:
            # Each option should have value and label
            assert "value" in option
            assert "label" in option

            # Value should be a string key
            assert isinstance(option["value"], str)
            assert len(option["value"]) > 0

            # Label should be human-readable
            assert isinstance(option["label"], str)
            assert len(option["label"]) > 0
